package org.apache.ocean;

import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.time.DurationFormatUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.search.ConstantScoreRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
import org.apache.ocean.cache.Cache;
import org.apache.ocean.server.DateFieldType;
import org.apache.ocean.server.DoubleFieldType;
import org.apache.ocean.server.FieldType;
import org.apache.ocean.server.LongFieldType;
import org.apache.ocean.server.SchemaField;
import org.apache.ocean.server.StringFieldType;
import org.apache.ocean.server.TextField;
import org.apache.ocean.util.DistanceUtils;
import org.apache.ocean.util.GlobUtil;

import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.ULocale;

/**
 * 
 * @author jason rutherglen
 */
public class ClusterUtil implements ClusterConstants {
	public static Logger log = Logger.getLogger(ClusterUtil.class.getName());
	public static String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
	// public static FastDateFormat dateFormat =
	// FastDateFormat.getInstance(DATE_PATTERN);
	public final static Pattern splitPattern = Pattern.compile(",| ");

	public ClusterUtil() {
	}

	public static boolean debugIndexWriter() {
		return false;
	}

	public static boolean debugHttp() {
		return false;
	}

	public static String requestToString(HttpServletRequest request) {
		NamedList<String, String> namedList = new NamedList<String, String>();
		List<String> names = Collections.list(request.getParameterNames());
		for (String name : names) {
			String[] array = request.getParameterValues(name);
			if (array != null) {
				for (String value : array) {
					namedList.add(name, value);
				}
			}
		}
		return namedList.toString();
	}

	public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
		if (coll == null)
			return null;
		if (coll.size() == 0)
			return null;
		return Collections.max(coll);
	}

	public static String getMaxTerm(String field, List<MultiIndexSearcher> indexSearchers) throws Exception {
		String currentMaxIndexed = null;
		for (MultiIndexSearcher indexSearcher : indexSearchers) {
			String maxIndexed = indexSearcher.getMaxFieldValue(field);
			if (maxIndexed == null || currentMaxIndexed == null) {
				currentMaxIndexed = maxIndexed;
			} else if (currentMaxIndexed != null && maxIndexed.compareTo(currentMaxIndexed) > 0) {
				currentMaxIndexed = maxIndexed;
			}
		}
		return currentMaxIndexed;
	}

	public static boolean isOverDuration(long maxDuration, Date date, Date timestamp) {
		if (date == null)
			return false;
		if (timestamp.getTime() - date.getTime() > maxDuration) {
			return true;
		} else
			return false;
	}

	public static List<Term> getFieldTerms(String f, IndexReader indexReader) throws Exception {
		String field = f.intern();
		TermEnum termEnum = indexReader.terms(new Term(field, ""));
		List<Term> terms = new ArrayList<Term>();
		try {
			do {
				Term term = termEnum.term();
				if (term == null || term.field() != field)
					break;
				String text = term.text();
				terms.add(new Term(field, text));
			} while (termEnum.next());
		} finally {
			termEnum.close();
		}
		return terms;
	}

	public static List<String> getParameters(String globPattern, HttpServletRequest request) {
		GlobUtil globUtil = new GlobUtil();
		globUtil.setCaseSensitive(false);
		globUtil.setFrom(globPattern);
		globUtil.setTo("*");
		List<String> parameterList = Collections.list(request.getParameterNames());
		List<String> list = new ArrayList<String>();
		for (String parameterName : parameterList) {
			String result = globUtil.mapFileName(parameterName);
			if (result != null)
				list.add(parameterName);
		}
		return list;
	}

	public static List<ShardInfo.Index.IndexFile> createIndexFiles(Directory directory) throws Exception {
		String[] fileArray = directory.list();
		List<ShardInfo.Index.IndexFile> indexFiles = new ArrayList<ShardInfo.Index.IndexFile>(fileArray.length);
		for (String name : fileArray) {
			ShardInfo.Index.IndexFile indexFile = new ShardInfo.Index.IndexFile();
			indexFile.name = name;
			indexFile.size = new CLong(directory.fileLength(name));
			indexFile.lastModified = new CLong(directory.fileModified(name));
			indexFiles.add(indexFile);
		}
		return indexFiles;
	}

	public static Query parseQuery(String queryString, IndexSchema indexSchema) throws Exception {
		return OceanQueryParser.parse(queryString, indexSchema);
	}

	public static Document toDocument(OceanDocument oceanDocument, IndexSchema indexSchema) throws Exception {
		Document document = new Document();
		for (NamedList.Entry<String, Object> entry : oceanDocument.getEntries()) {
			String fieldName = entry.key;
			if (entry.value == null)
				continue;
			String fieldValue = null;
			if (entry.value instanceof Date) {
				fieldValue = ClusterUtil.toDateString((Date) entry.value);
			} else if (entry.value instanceof CDouble) {
				fieldValue = entry.value.toString();
			} else if (entry.value instanceof CLong) {
				fieldValue = entry.value.toString();
			} else if (entry.value instanceof String) {
				fieldValue = (String) entry.value;
			} else {
				throw new Exception("unknown type: " + entry.value.getClass().getName());
			}
			SchemaField schemaField = indexSchema.getField(fieldName);
			FieldType fieldType = schemaField.getFieldType();
			Fieldable fieldable = fieldType.createField(schemaField, fieldValue, 1.0f);
			document.add(fieldable);
		}
		return document;
	}

	public static Object toObjectFromIndexed(String fieldName, String indexedValue, IndexSchema indexSchema) throws Exception {
		SchemaField schemaField = indexSchema.getField(fieldName);
		FieldType fieldType = schemaField.getFieldType();
		String readable = fieldType.indexedToReadable(indexedValue);
		if (fieldType instanceof LongFieldType) {
			return new CLong(readable);
		} else if (fieldType instanceof DoubleFieldType) {
			return new CDouble(readable);
		} else if (fieldType instanceof StringFieldType) {
			return readable;
		} else if (fieldType instanceof TextField) {
			return readable;
		} else {
			throw new Exception("unknown type");
		}
	}

	public static CSortFields toSortFields(String string) {
		if (StringUtils.isBlank(string))
			return null;
		String[] array = StringUtils.split(string.trim(), ",");
		if (array == null || array.length == 0)
			return null;
		CSortFields sortFields = new CSortFields();
		for (String sortParam : array) {
			String[] paramArray = StringUtils.split(sortParam.trim(), " ");
			if (paramArray == null || paramArray.length == 0)
				continue;
			String field = paramArray[0];
			String order = "asc";
			if (paramArray.length > 1)
				order = paramArray[1];
			boolean reverse = false;
			if (StringUtils.equalsIgnoreCase("asc", order)) {
				reverse = false;
			} else if (StringUtils.equalsIgnoreCase("desc", order)) {
				reverse = true;
			}
			CSortField sortField = new CSortField(field, reverse);
			sortFields.add(sortField);
		}
		return sortFields;
	}

	public static ULocale getDefaultLocale() {
		return ULocale.US;
	}

	public static IndexReader getIndexReaderBySegmentName(String segmentName, IndexReader indexReader) throws Exception {
		if (indexReader.getClass().getName().equals("org.apache.lucene.index.SegmentReader")) {
			String readerSegmentName = (String) FieldUtil.getFieldValue("segment", indexReader);
			if (StringUtils.equals(segmentName, readerSegmentName)) {
				return indexReader;
			}
			return null;
		} else if (indexReader.getClass().getName().equals("org.apache.lucene.index.MultiSegmentReader")) {
			IndexReader[] subReaders = (IndexReader[]) FieldUtil.getFieldValue("subReaders", indexReader);
			if (subReaders == null)
				return null;
			for (IndexReader subReader : subReaders) {
				String readerSegmentName = (String) FieldUtil.getFieldValue("segment", indexReader);
				if (StringUtils.equals(segmentName, readerSegmentName)) {
					return indexReader;
				}
			}
			return null;
		}
		throw new Exception("unknown reader class: " + indexReader.getClass().getName());
	}

	public static List<OceanDocument> toOceanDocuments(List<CScoreDocument> scoreDocuments, IndexSchema indexSchema) throws Exception {
		List<OceanDocument> oceanDocuments = new ArrayList<OceanDocument>(scoreDocuments.size());
		for (CScoreDocument scoreDocument : scoreDocuments) {
			OceanDocument oceanDocument = scoreDocument.toOceanDocument(indexSchema);
			oceanDocuments.add(oceanDocument);
		}
		return oceanDocuments;
	}

	public static OceanDocument toOceanDocument(Document document, IndexSchema indexSchema) throws Exception {
		if (document == null)
			return null;
		NamedList<String, String> namedList = toNamedList(document);
		OceanDocument oceanDocument = new OceanDocument();
		for (NamedList.Entry<String, String> entry : namedList.getEntries()) {
			String fieldName = entry.key;
			String indexedValue = entry.value;
			if (indexedValue == null)
				continue;
			SchemaField schemaField = indexSchema.getField(fieldName);
			Object value = null;
			FieldType fieldType = schemaField.getFieldType();
			String readableValue = fieldType.indexedToReadable(indexedValue);
			if (fieldType instanceof DateFieldType) {
				value = ClusterUtil.parseDateDocument(readableValue);
			} else if (fieldType instanceof DoubleFieldType) {
				value = new CDouble(readableValue);
			} else if (fieldType instanceof LongFieldType) {
				value = new CLong(readableValue);
			} else {
				value = readableValue;
			}
			// String readable = ClusterUtil.indexedToReadable(fieldName,
			// indexedValue, indexSchema);
			oceanDocument.add(fieldName, value);
		}
		return oceanDocument;
	}

	public static String toString(Document document) {
		return toNamedList(document).toString();
	}

	public static NamedList<String, String> toNamedList(Document document) {
		List<org.apache.lucene.document.Field> fields = document.getFields();
		SortedSet<String> fieldNames = new TreeSet<String>();
		for (org.apache.lucene.document.Field field : fields) {
			fieldNames.add(field.name());
		}
		NamedList<String, String> namedList = new NamedList<String, String>(fields.size());
		for (String fieldName : fieldNames) {
			org.apache.lucene.document.Field[] fieldArray = document.getFields(fieldName);
			for (org.apache.lucene.document.Field field : fieldArray) {
				namedList.add(field.name(), field.stringValue());
			}
		}
		return namedList;
	}

	public static File getNameHome() throws Exception {
		File defaultDirectory = new File(AppServerUtil.getAppServerHome(), "oceanname");
		return getHomeDirectory("namehome", defaultDirectory);
	}

	public static File getMergeHome() throws Exception {
		File defaultDirectory = new File(AppServerUtil.getAppServerHome(), "oceanmerge");
		return getHomeDirectory("oceanmerge", defaultDirectory);
	}

	// TODO: test out in server.xml
	public static File getOceanHome() throws Exception {
		File defaultDirectory = new File(AppServerUtil.getAppServerHome(), "ocean");
		return getHomeDirectory("oceanhome", defaultDirectory);
	}

	public static File getHomeDirectory(String name, File defaultDirectory) throws Exception {
		String value = System.getenv(name);
		if (value != null)
			return new File(value);
		value = System.getProperty(name);
		if (value != null)
			return new File(value);
		Context context = (Context) new InitialContext().lookup("java:comp/env");
		try {
			String string = (String) context.lookup(name);
			if (StringUtils.isNotBlank(value))
				return new File(value);
		} catch (NameNotFoundException nameNotFoundException) {
		}
		defaultDirectory.mkdirs();
		return defaultDirectory;
	}

	public static List reflectFindFields(String fieldName, Object whereValue, Collection collection) throws Exception {
		List list = new ArrayList();
		for (Object object : collection) {
			Object fieldValue = FieldUtil.getFieldValue(fieldName, object);
			if (ObjectUtils.equals(fieldValue, whereValue)) {
				list.add(fieldValue);
			}
		}
		return list;
	}

	public static boolean isStatusOnline(String status) {
		if (StringUtils.equals(status, ClusterConstants.SLAVE))
			return true;
		if (StringUtils.equals(status, ClusterConstants.MASTER))
			return true;
		return false;
	}

	public static String toDurationWords(java.util.Date date) {
		if (date == null)
			return "";
		long durationMillis = System.currentTimeMillis() - date.getTime();
		return DurationFormatUtils.formatDurationWords(durationMillis, true, true);
	}

	public static List<String> toList(String[] array) {
		if (array == null)
			return new ArrayList<String>(0);
		return Arrays.asList(array);
	}

	public static List executeTasks(List<Callable<Object>> tasks, ExecutorService executor, TimeoutState timeoutState) throws Exception {
		List results = new ArrayList();
		List<Future<Object>> futures = executor.invokeAll(tasks);
		while (timeoutState.isOK() && tasks.size() > results.size()) {
			for (Future future : futures) {
				if (timeoutState.notOK()) {
					throw new GeneralTimeoutException(timeoutState);
				}
				results.add(future.get());
			}
		}
		return results;
	}

	public static void throwException(ExecutionException executionException) throws Exception {
		Throwable throwable = executionException.getCause();
		if (throwable instanceof Exception) {
			throw (Exception) throwable;
		} else {
			throw new Exception(throwable);
		}
	}

	public static Object verifyClassType(Object object, String otherClassName) throws RuntimeException {
		String className = object.getClass().getName();
		if (!StringUtils.equals(className, otherClassName)) {
			throw new RuntimeException(className + " not same as " + otherClassName);
		}
		return object;
	}

	public static Class findClass(String name, String... packageHints) throws Exception {
		List<String> classNames = new ArrayList<String>(packageHints.length + 1);
		classNames.add(name);
		for (String packageHint : packageHints) {
			classNames.add(packageHint + '.' + name);
		}
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		for (String className : classNames) {
			try {
				return Class.forName(className, true, classLoader);
			} catch (ClassNotFoundException classNotFoundException) {
			}
		}
		throw new Exception(name + " not found");
	}

	public static Map reflectToMap(String fieldName, List list) throws Exception {
		Map map = new HashMap(list.size());
		for (Object object : list) {
			Object key = FieldUtil.getFieldValue(fieldName, object);
			if (key != null) {
				map.put(key, object);
			}
		}
		return map;
	}

	public static Object construct(Object parameter, Class clazz) throws Exception {
		Constructor constructor = clazz.getDeclaredConstructor(parameter.getClass());
		try {
			return constructor.newInstance(parameter);
		} catch (InvocationTargetException invocationTargetException) {
			Throwable cause = invocationTargetException.getCause();
			if (cause instanceof Exception)
				throw (Exception) cause;
			else
				throw invocationTargetException;
		}
	}

	public static Object reflectFindByField(String fieldName, Object toFind, List list) throws Exception {
		for (Object object : list) {
			Object value = FieldUtil.getFieldValue(fieldName, object);
			if (ObjectUtils.equals(value, toFind)) {
				return object;
			}
		}
		return null;
	}

	public static boolean isTrue(String string) {
		return StringUtils.equalsIgnoreCase("true", string);
	}

	public static void createDistanceBoxQueries(Distance distance, List<Query> filterQueries, IndexSchema indexSchema) {
		if (distance == null) {
			return;
		}
		Rectangle2D box = DistanceUtils.getBoundary(distance.latitude.value, distance.longitude.value, distance.distance.value);
		double x1 = box.getY(); //
		double y1 = box.getX(); //
		double x2 = box.getMaxY();
		double y2 = box.getMaxX();
		String x1String = ClusterUtil.readableToIndexed(ClusterConstants.LATITUDE_FIELD, Double.toString(x1), indexSchema);
		String x2String = ClusterUtil.readableToIndexed(ClusterConstants.LATITUDE_FIELD, Double.toString(x2), indexSchema);
		String y1String = ClusterUtil.readableToIndexed(ClusterConstants.LONGITUDE_FIELD, Double.toString(y1), indexSchema);
		String y2String = ClusterUtil.readableToIndexed(ClusterConstants.LONGITUDE_FIELD, Double.toString(y2), indexSchema);
		filterQueries.add(new ConstantScoreRangeQuery(ClusterConstants.LATITUDE_FIELD, x1String, x2String, true, true));
		filterQueries.add(new ConstantScoreRangeQuery(ClusterConstants.LONGITUDE_FIELD, y1String, y2String, true, true));
	}

	public static long getSize(org.apache.lucene.store.Directory directory) throws Exception {
		long total = 0;
		for (String file : directory.list()) {
			total += directory.fileLength(file);
		}
		return total;
	}

	public static String join(String str1, String str2, String separator) {
		if (StringUtils.isBlank(str2)) {
			return str1;
		}
		return str1 + separator + str2;
	}

	public static Object[] parseTermFreq(String string) {
		int fstart = string.indexOf(":", 0);
		String field = string.substring(0, fstart);
		int tstart = string.indexOf(":", fstart + 1);
		String freqString = string.substring(fstart + 1, tstart);
		AtomicLong freq = new AtomicLong(Long.parseLong(freqString));
		String text = string.substring(tstart + 1, string.length());
		return new Object[] { new Term(field, text), freq };
	}

	public static com.ibm.icu.util.ULocale getLocale(Map<String, String> args) {
		String localeString = args.get("locale");
		if (StringUtils.isNotBlank(localeString)) {
			return new com.ibm.icu.util.ULocale(localeString);
		} else {
			return new com.ibm.icu.util.ULocale("en_US");
		}
	}

	public static Document getDocument(int doc, IndexReader indexReader, FieldSelector fieldSelector, Set<String> fieldCacheFields, Cache cache) throws Exception {
		Document document = indexReader.document(doc, fieldSelector);
		for (String field : fieldCacheFields) {
			String[] stringArray = cache.getStringArray(field, indexReader);
			document.add(new Field(field, stringArray[doc], Field.Store.YES, Field.Index.NO));
		}
		return document;
	}

	public static Date parseDate(String string) {
		return parseDate(string, createDateFormats());
	}

	public static Date parseDateDocument(String string) {
		return parseDate(string, createDateFormatsParse());
	}

	public static Date parseDate(String string, com.ibm.icu.text.SimpleDateFormat[] dateFormats) {
		Date date = null;
		if (StringUtils.isBlank(string))
			return null;
		for (com.ibm.icu.text.SimpleDateFormat dateFormat : dateFormats) {
			try {
				date = dateFormat.parse(string);
				return date;
			} catch (java.text.ParseException parseException) {
				log.log(Level.SEVERE, "string: " + string + " " + ExceptionUtils.getFullStackTrace(parseException));
			}
		}
		return null;
	}

	public static com.ibm.icu.text.SimpleDateFormat[] createDateFormatsParse() {
		com.ibm.icu.text.SimpleDateFormat[] dateFormats = new com.ibm.icu.text.SimpleDateFormat[] {
				new com.ibm.icu.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"), new com.ibm.icu.text.SimpleDateFormat("yyyyyy-MM-dd'T'HH:mm:ss") };
		return dateFormats;
	}

	// TODO: unit test date parsing
	public static com.ibm.icu.text.SimpleDateFormat[] createDateFormats() {
		com.ibm.icu.text.SimpleDateFormat[] dateFormats = new com.ibm.icu.text.SimpleDateFormat[] {
				new com.ibm.icu.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), new com.ibm.icu.text.SimpleDateFormat("yyyyyy-MM-dd'T'HH:mm:ss'Z'") };
		return dateFormats;
	}

	/**
	 * public static List<CScoreDoc> toScoreDocs(long start, long length,
	 * org.apache.lucene.util.PriorityQueue queue) { int queueSize = queue.size();
	 * if (queueSize == 0) { return new ArrayList<CScoreDoc>(0); } if (start+1 >
	 * queueSize) { return new ArrayList<CScoreDoc>(0); } //CScoreDoc[] scoreDocs =
	 * new CScoreDoc[queue.size()]; int numToGet = queue.size() - (int)start; List<CScoreDoc>
	 * list = new ArrayList<CScoreDoc>(); CScoreDoc scoreDoc = null; for (int i =
	 * (int) numToGet - 1; i >= 0; i--) { scoreDoc = (CScoreDoc)queue.pop();
	 * list.add(scoreDoc); //scoreDocs[i] = scoreDoc; } Collections.reverse(list);
	 * return list; //log.info("queue.size: "+queue.size()+" list.size:
	 * "+list.size()); //List<CScoreDoc> subList = list.subList((int)start,
	 * (int)start+(int)length-1); //List<CScoreDoc> finalList = new ArrayList<CScoreDoc>();
	 * //for (CScoreDoc sd : subList) { // if (sd != null) finalList.add(sd); //
	 * else break; //} //return finalList; }
	 */
	/**
	 * public static List<ScoreDoc> sliceScoreDocs(int start, int length,
	 * CScoreDoc[] scoreDocs) { if (scoreDocs.length == 0) return new ArrayList<ScoreDoc>(0);
	 * int total = start + length; if (scoreDocs.length == total) { } else { } }
	 */

	public static List<CScoreDoc> sliceScoreDocs(int start, int length, CScoreDoc[] scoreDocs) {
		if (start + 1 > scoreDocs.length || length == 0)
			return new ArrayList<CScoreDoc>(0);
		List<CScoreDoc> list = new ArrayList<CScoreDoc>(length);
		int count = 0;
		for (int x = start; x < scoreDocs.length; x++) {
			if (count == length)
				break;
			list.add(scoreDocs[x]);
			count++;
		}
		return list;
	}

	public static List<CScoreDoc> toScoreDocs(int start, int length, org.apache.lucene.util.PriorityQueue queue) {
		CScoreDoc[] array = toScoreDocs(queue);
		return sliceScoreDocs(start, length, array);
	}

	public static CScoreDoc[] toScoreDocs(org.apache.lucene.util.PriorityQueue queue) {
		int queueSize = queue.size();
		CScoreDoc[] scoreDocs = new CScoreDoc[queueSize];
		CScoreDoc scoreDoc = null;
		for (int i = (int) queueSize - 1; i >= 0; i--) {
			scoreDoc = (CScoreDoc) queue.pop();
			scoreDocs[i] = scoreDoc;
		}
		return scoreDocs;
	}

	public static void testWriteableDirectory(File directory) throws Exception {
		File testFile = new File(directory, "test.file");
		FileUtils.writeStringToFile(testFile, "test file", "UTF-8");
		if (!testFile.exists()) {
			throw new Exception("unable to write to: " + directory.getAbsolutePath());
		} else {
			testFile.delete();
		}
	}

	public static org.jdom.Element createUpdateElement(boolean set, org.jdom.Element contentElement) throws Exception {
		org.jdom.Element updateElement = new org.jdom.Element("update");
		updateElement.setAttribute("set", Boolean.toString(set));
		updateElement.addContent(contentElement);
		return updateElement;
	}

	public static Set<URL> loadURLs(File file) throws Exception {
		List lines = FileUtils.readLines(file, "UTF-8");
		Set<URL> urls = new HashSet<URL>(lines.size());
		for (Object lineObject : lines) {
			urls.add(new URL((String) lineObject));
		}
		return urls;
	}

	public static void sendError(Throwable throwable, PrintWriter writer, HttpServletResponse response) {
		try {
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			String stackTrace = org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace(throwable);
			writer.write(stackTrace);
		} catch (Throwable exception) {
			log.log(Level.SEVERE, "", exception);
		}
	}

	public static void sendError(String msg, HttpServletResponse response) {
		try {
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			PrintWriter writer = response.getWriter();
			writer.write(msg);
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
		}
	}

	/**
	 * public static Cell loadCell(File cellFile) throws Exception { if
	 * (!cellFile.exists()) return null; org.jdom.Element element =
	 * XMLUtil.parseElement(cellFile); Cell cell = new Cell(element); return cell; }
	 * public static void saveCell(Cell cell, File cellFile) throws Exception {
	 * XMLUtil.saveXML(cell.toElement(), cellFile); }
	 */
	public static String loadNodeID(File nodeIDFile) throws Exception {
		if (!nodeIDFile.exists()) {
			String uuid = java.util.UUID.randomUUID().toString();
			saveNodeID(uuid, nodeIDFile);
		}
		return FileUtils.readFileToString(nodeIDFile);
	}

	public static void saveNodeID(String nodeID, File nodeIDFile) throws Exception {
		FileUtils.writeStringToFile(nodeIDFile, nodeID);
	}

	public static void addHeader(String name, Object value, HttpServletResponse response) {
		if (value == null) {
			return;
		}
		if (value instanceof String) {
			String string = (String) value;
			if (StringUtils.isBlank(string)) {
				return;
			}
		}
		String string = null;
		if (value instanceof Date) {
			Date date = (Date) value;
			string = DateParser.formatRFC822(date);
		} else if (value instanceof String) {
			string = (String) value;
		} else {
			string = value.toString();
		}
		response.addHeader(name, string);
	}

	public static Term toIDVersionTerm(CLong id, CLong version) {
		return new Term(ClusterConstants.ID_VERSION_FIELD, toString(id, version));
	}

	public static String toString(CLong id, CLong version) {
		StringBuilder builder = new StringBuilder(25);
		builder.append(id.value);
		builder.append("_");
		builder.append(version.value);
		return builder.toString();
	}

	/**
	 * public static Set<String> difference(Set<String> set1, Set<String> set2) {
	 * Set<String> newSet = new HashSet<String>(set1.size()); for (String
	 * string1 : set1) { //if (set2.contains(string1)) newSet.add(string1); }
	 * return newSet; }
	 */
	public static class Intersection {
		public Set<String> same;
		public Set<String> different1;
	}

	public static Intersection intersection(Set<String> set1, Set<String> set2) {
		Intersection intersection = new Intersection();
		intersection.same = new HashSet<String>(set1.size());
		intersection.different1 = new HashSet<String>(set1.size());
		for (String string1 : set1) {
			if (set2.contains(string1)) {
				intersection.same.add(string1);
			} else {
				intersection.different1.add(string1);
			}
		}
		return intersection;
	}

	public static SortedSet<String> splitSortedSet(String string) {
		if (StringUtils.isBlank(string)) {
			return null;
		}
		String[] array = splitPattern.split(string.trim(), 0);
		TreeSet<String> sortedSet = new TreeSet<String>();
		for (int x = 0; x < array.length; x++) {
			sortedSet.add(array[x]);
		}
		return sortedSet;
	}

	public static List<String> split(String string) {
		if (StringUtils.isBlank(string)) {
			return null;
		}
		String[] array = splitPattern.split(string.trim(), 0);
		return Arrays.asList(array);
	}

	public static Map<String, String> toMapExcept(org.jdom.Element element, String exceptName) {
		Map<String, String> map = new HashMap<String, String>();
		for (Object object : element.getAttributes()) {
			org.jdom.Attribute attribute = (org.jdom.Attribute) object;
			String name = attribute.getName();
			if (!StringUtils.equals(name, exceptName)) {
				map.put(name, attribute.getValue());
			}
		}
		return map;
	}

	public static Boolean toBooleanObject(String string, Boolean defaultValue) {
		Boolean value = BooleanUtils.toBooleanObject(string);
		if (value == null) {
			return defaultValue;
		}
		return value;
	}

	public static boolean getBoolean(Boolean value) {
		if (value == null) {
			return false;
		}
		return value.booleanValue();
	}

	public static TransactionRecord createTransactionRecordSuccess(Document document, IndexSchema indexSchema) {
		TransactionRecord transactionRecord = new TransactionRecord();
		transactionRecord.documentID = ClusterUtil.getID(document, indexSchema);
		transactionRecord.id = ClusterUtil.getTransactionID(document, indexSchema);
		transactionRecord.status = TransactionRecord.SUCCESS;
		return transactionRecord;
	}

	public static TransactionRecord createTransactionRecordFailure(Document document, Throwable throwable, IndexSchema indexSchema) {
		TransactionRecord transactionRecord = new TransactionRecord();
		transactionRecord.documentID = ClusterUtil.getID(document, indexSchema);
		transactionRecord.id = ClusterUtil.getTransactionID(document, indexSchema);
		transactionRecord.stackTrace = ExceptionUtils.getFullStackTrace(throwable);
		transactionRecord.status = TransactionRecord.FAILURE;
		return transactionRecord;
	}

	public static void main(String[] args) {
		try {
			org.jdom.Element parentElement = new org.jdom.Element("parent");
			org.jdom.Element childElement = new org.jdom.Element("child");
			parentElement.addContent(childElement);

			log.log(Level.INFO, "" + XMLUtil.outputElement(parentElement));
			org.jdom.Element removeParentElement = removeParentElement(childElement);
			log.log(Level.INFO, "" + XMLUtil.outputElement(removeParentElement));
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
		}
	}

	public static String toString(Term term, AtomicLong atomicLong) {
		return term.field() + ":" + term.text() + ":" + atomicLong;
	}

	public static String concat(Collection<String> list, String separator) {
		if (list == null || list.size() == 0)
			return null;
		StringBuilder buffer = new StringBuilder(60);
		int size = list.size();
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			buffer.append(iterator.next());
			if (iterator.hasNext()) {
				buffer.append(separator);
			}
		}
		return buffer.toString();
	}

	public static String concat(String main, String separator, List<String> list) {
		StringBuilder buffer = new StringBuilder(60);
		int size = list.size();
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			buffer.append(iterator.next());
			buffer.append(main);
			if (iterator.hasNext()) {
				buffer.append(separator);
			}
		}
		return buffer.toString();
	}

	public static String concat(String main, String separator, int count) {
		StringBuilder buffer = new StringBuilder(60);
		for (int x = 0; x < count; x++) {
			buffer.append(main);
			if (x < count - 1) {
				buffer.append(separator);
			}
		}
		return buffer.toString();
	}

	public static void printSystemProperties(Logger log) {
		StringBuilder buffer = new StringBuilder();
		for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			buffer.append(key + ": " + value + "\n");
		}
		log.log(Level.INFO, buffer.toString());
	}

	public static org.jdom.Element removeParentElement(org.jdom.Element element) {
		org.jdom.Parent parent = element.getParent();
		if (parent == null) {
			return element;
		}
		List list = parent.removeContent();
		org.jdom.Element childElement = (org.jdom.Element) getFirst(list);
		return childElement;
	}

	public static Object getFirst(List list) {
		Iterator iterator = list.iterator();
		if (iterator.hasNext()) {
			return iterator.next();
		} else {
			return null;
		}
	}

	public static InetSocketAddress getLocalSocketAddress(String host) {
		InetSocketAddress address = new InetSocketAddress(host, getLocalPort());
		return address;
	}

	public static int getLocalPort() {
		return RandomUtils.nextInt(9000) + 9000;
	}

	public static URL parseURL(String string) throws MalformedURLException {
		if (StringUtils.isBlank(string)) {
			return null;
		}
		return new URL(string);
	}

	public static TransactionID parseTransactionID(String value) {
		if (StringUtils.isBlank(value)) {
			return null;
		}
		return new TransactionID(value);
	}

	public static void close(ServerSocket socket) {
		try {
			socket.close();
		} catch (Throwable throwable) {
		}
	}

	public static void close(InputStream input, OutputStream output, Socket socket) {
		IOUtils.closeQuietly(input);
		IOUtils.closeQuietly(output);
		try {
			socket.close();
		} catch (Throwable throwable) {
		}
	}

	public static void deleteDirectory(File directory) throws Exception {
		if (SystemUtils.IS_OS_WINDOWS) {
			String windowsCMD = "del /Q " + directory.getAbsolutePath();
			ExternalProcess externalProcess = new ExternalProcess(windowsCMD);
			log.log(Level.INFO, externalProcess.toString());
		} else {
			String unixCMD = "rm -rf " + directory.getAbsolutePath();
			ExternalProcess externalProcess = new ExternalProcess(unixCMD);
			log.log(Level.INFO, externalProcess.toString());
		}
	}

	public static org.apache.lucene.index.Term getTerm(String string) {
		if (StringUtils.isBlank(string)) {
			return null;
		}
		int fstart = string.indexOf(":", 0);
		String field = string.substring(0, fstart);
		String text = string.substring(fstart + 1, string.length());
		return new org.apache.lucene.index.Term(field, text);
	}

	public static ShardTerm getShardTerm(String string, IndexSchema indexSchema) {
		int fstart = string.indexOf(":", 0);
		String shardID = string.substring(0, fstart);
		int tstart = string.indexOf(":", fstart + 1);
		String field = string.substring(fstart + 1, tstart);
		String text = string.substring(tstart + 1, string.length());

		SchemaField schemaField = indexSchema.getField(field);
		String internalText = schemaField.getFieldType().toInternal(text);
		Term term = new org.apache.lucene.index.Term(field, internalText);
		return new ShardTerm(field, term);
	}

	public static org.apache.lucene.index.Term getTerm(String string, IndexSchema indexSchema) {
		if (StringUtils.isBlank(string)) {
			return null;
		}
		int fstart = string.indexOf(":", 0);
		String field = string.substring(0, fstart);
		String text = string.substring(fstart + 1, string.length());
		SchemaField schemaField = indexSchema.getField(field);
		String internalText = schemaField.getFieldType().toInternal(text);
		return new org.apache.lucene.index.Term(field, internalText);
	}

	public static String toString(org.apache.lucene.index.Term term) {
		String field = term.field();
		String text = term.text();
		StringBuilder builder = new StringBuilder(field.length() + 4 + text.length());
		builder.append(field).append(":").append(text);
		return builder.toString();
	}

	public static String toString(Date date, java.text.DateFormat dateFormat) {
		if (date == null) {
			return "";
		}
		return dateFormat.format(date);
	}

	public static String toString(Object object) {
		if (object == null) {
			return "";
		}
		if (object instanceof String) {
			String string = (String) object;
			if (StringUtils.isBlank(string)) {
				return "";
			}
			return string;
		} else {
			return object.toString();
		}
	}

	/**
	 * public static Node getNodeByRole(String role, List<Node> nodes) { for
	 * (Node node : nodes) { if (StringUtils.equals(node.role, role)) { return
	 * node; } } return null; }
	 */
	public static CLong getLong(String name, HttpServletRequest request) {
		return parseLong(request.getParameter(name));
	}

	public static TransactionID getTransactionID(String name, HttpServletRequest request) {
		String value = request.getParameter(name);
		return TransactionID.parse(value);
	}

	public static List<String> readLines(Reader reader) throws Exception {
		return IOUtils.readLines(reader);
	}

	public static boolean isNotBlank(String name, HttpServletRequest request) {
		return StringUtils.isNotBlank(request.getParameter(name));
	}

	public static void addParameter(String name, Object value, List<NameValuePair> parameters) {
		if (value == null) {
			return;
		}
		parameters.add(new NameValuePair(name, value.toString()));
	}

	public static SortedSet<String> splitToSortedSet(String string) {
		if (StringUtils.isBlank(string)) {
			return new TreeSet<String>();
		} else {
			String[] array = StringUtils.split(string, ", ");
			TreeSet<String> fields = new TreeSet<String>();
			for (String field : array) {
				fields.add(field);
			}
			return fields;
		}
	}

	public static Set<String> splitToSet(String string) {
		if (StringUtils.isBlank(string)) {
			return new HashSet<String>(0);
		} else {
			String[] array = StringUtils.split(string, ", ");
			Set<String> fields = new HashSet<String>(array.length);
			for (String field : array) {
				fields.add(field);
			}
			return fields;
		}
	}

	public static String toDateString(Date date) {
		try {
			SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
			return dateFormat.format(date);
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
			return null;
		}
	}

	public static Date getDateModified(Document document) {
		String dateModifiedString = document.get(DATE_MODIFIED_FIELD);
		if (StringUtils.isBlank(dateModifiedString)) {
			return null;
		}
		return ClusterUtil.parseDateDocument(dateModifiedString);
	}

	public static Date getDateCreated(Document document, IndexSchema indexSchema) {
		String readable = ClusterUtil.indexedToReadable(DATE_CREATED_FIELD, document, indexSchema);
		if (StringUtils.isBlank(readable)) {
			return null;
		}
		return ClusterUtil.parseDateDocument(readable);
	}

	public static void setFieldValue(String field, Object object, Document document, IndexSchema indexSchema) {
		String string = null;
		if (object != null) {
			string = object.toString();
		}
		setFieldValue(field, string, document, indexSchema);
	}

	public static String readableToIndexed(String field, String value, IndexSchema indexSchema) {
		SchemaField schemaField = indexSchema.getField(field);
		FieldType fieldType = schemaField.getFieldType();
		return fieldType.toInternal(value);
	}

	public static void setFieldValue(String field, String value, Document document, IndexSchema indexSchema) {
		document.removeFields(field);
		if (value == null) {
			return;
		}
		SchemaField schemaField = indexSchema.getField(field);
		FieldType fieldType = schemaField.getFieldType();
		Fieldable fieldable = fieldType.createField(schemaField, value, 1.0f);
		document.add(fieldable);
	}

	public static void setIndexSchemaVersion(String version, Document document, IndexSchema indexSchema) {
		setFieldValue(SCHEMA_VERSION_FIELD, version, document, indexSchema);
	}

	public static void setDateModified(Date dateModified, Document document, IndexSchema indexSchema) {
		String dateString = toDateString(dateModified);
		setFieldValue(DATE_MODIFIED_FIELD, dateString, document, indexSchema);
	}

	public static void setDateCreated(Date dateCreated, Document document, IndexSchema indexSchema) {
		String dateString = toDateString(dateCreated);
		setFieldValue(DATE_CREATED_FIELD, dateString, document, indexSchema);
	}

	public static void setShardID(String shardID, Document document, IndexSchema indexSchema) {
		setFieldValue(SHARD_ID_FIELD, shardID, document, indexSchema);
	}

	public static void setCellID(String cellID, Document document, IndexSchema indexSchema) {
		setFieldValue(CELL_ID_FIELD, cellID, document, indexSchema);
	}

	public static void setDistance(CDouble distance, Document document, IndexSchema indexSchema) {
		setFieldValue(DISTANCE_FIELD, distance, document, indexSchema);
	}

	public static void setCellTransactionID(TransactionID cellTransactionID, Document document, IndexSchema indexSchema) {
		setFieldValue(CELL_TRANSACTION_ID_FIELD, cellTransactionID, document, indexSchema);
	}

	public static void setTransactionID(TransactionID id, Document document, IndexSchema indexSchema) {
		setFieldValue(TRANSACTION_ID_FIELD, id, document, indexSchema);
	}

	public static void setIDVersion(CLong id, CLong version, Document document, IndexSchema indexSchema) {
		if (id == null) {
			throw new IllegalArgumentException("id null");
		}
		if (version == null) {
			throw new IllegalArgumentException("version null");
		}
		String idVersion = id + "_" + version;
		setFieldValue(ID_VERSION_FIELD, idVersion, document, indexSchema);
	}

	public static void setGUID(CLong id, CLong version, String shardID, Document document, IndexSchema indexSchema) {
		String guid = id + "_" + version + "_" + shardID;
		setFieldValue(GUID_FIELD, guid, document, indexSchema);
	}

	public static void setID(CLong id, Document document, IndexSchema indexSchema) {
		setFieldValue(ID_FIELD, id, document, indexSchema);
	}

	public static void setVersion(CLong version, Document document, IndexSchema indexSchema) {
		setFieldValue(VERSION_FIELD, version, document, indexSchema);
	}

	public static void setSchemaVersion(CLong schemaVersion, Document document, IndexSchema indexSchema) {
		setFieldValue(SCHEMA_VERSION_FIELD, schemaVersion, document, indexSchema);
	}

	public static boolean isDeletedDirectory(File directory) {
		File deletedFile = getDeletedFile(directory);
		return directory.getName().endsWith(".deleted") && deletedFile.exists();
	}

	public static File getDeletedFile(File directory) {
		File deleteFile = new File(directory, "cluster.deleted");
		return deleteFile;
	}

	public static CDouble parseDouble(String value) {
		if (StringUtils.isBlank(value)) {
			return null;
		}
		try {
			return new CDouble(value);
		} catch (Throwable ex) {
			return null;
		}
	}

	public static CLong parseLong(String value) {
		if (StringUtils.isBlank(value)) {
			return null;
		}
		try {
			return new CLong(value);
		} catch (Throwable ex) {
			return null;
		}
	}

	public static Integer parseInteger(String value) {
		if (StringUtils.isBlank(value)) {
			return null;
		}
		try {
			return new Integer(value);
		} catch (Throwable ex) {
			return null;
		}
	}

	public static void validateDocumentAdd(Document document, IndexSchema indexSchema) throws ClusterException {
		CLong id = getID(document, indexSchema);
		if (id != null) {
			throw new ClusterException("id should not be in document");
		}
		CLong version = getVersion(document, indexSchema);
		if (version != null) {
			throw new ClusterException("version should not be in document");
		}
		String shardID = getShardID(document);
		if (StringUtils.isNotBlank(shardID)) {
			throw new ClusterException("shardID should not be in document");
		}
	}

	public static void validateDocumentUpdate(Document document, IndexSchema indexSchema) throws ClusterException {
		CLong id = getID(document, indexSchema);
		if (id == null) {
			throw new ClusterException("id null");
		}
		CLong version = getVersion(document, indexSchema);
		if (version == null) {
			throw new ClusterException("version missing");
		}
	}

	public static CLong incrementDocumentVersion(Document document, IndexSchema indexSchema) throws ClusterException {
		CLong version = getVersion(document, indexSchema);
		if (version == null) {
			throw new ClusterException("version missing");
		}
		long newVersion = version.value + 1;
		ClusterUtil.setVersion(new CLong(newVersion), document, indexSchema);
		return new CLong(newVersion);
	}

	public static List<Float> createScores(List<? extends ScoreDoc> documents) {
		List<Float> scores = new ArrayList<Float>(documents.size());
		for (ScoreDoc scoreDoc : documents) {
			scores.add(scoreDoc.score);
		}
		return scores;
	}

	public static void throwIOException(ExecutionException executionException) throws IOException {
		Throwable cause = executionException.getCause();
		if (cause instanceof IOException) {
			IOException ioException = (IOException) cause;
			throw ioException;
		} else {
			IOException ioException2 = new IOException("");
			ioException2.initCause(cause);
			throw ioException2;
		}
	}

	public static String getCellID(Document document) {
		return document.get(CELL_ID_FIELD);
	}

	public static String getShardID(Document document) {
		return document.get(ClusterConstants.SHARD_ID_FIELD);
	}

	public static CLong getSchemaVersion(Document document, IndexSchema indexSchema) {
		String readable = indexedToReadable(ClusterConstants.SCHEMA_VERSION_FIELD, document, indexSchema);
		return new CLong(readable);
	}

	public static CLong getVersion(Document document, IndexSchema indexSchema) {
		String readable = indexedToReadable(VERSION_FIELD, document, indexSchema);
		return new CLong(readable);
	}

	public static String indexedToReadable(String fieldName, String indexed, IndexSchema indexSchema) {
		if (indexed == null)
			return null;
		SchemaField schemaField = indexSchema.getField(fieldName);
		FieldType fieldType = schemaField.getFieldType();
		String readable = fieldType.indexedToReadable(indexed);
		return readable;
	}

	public static String indexedToReadable(String fieldName, Document document, IndexSchema indexSchema) {
		SchemaField schemaField = indexSchema.getField(fieldName);
		FieldType fieldType = schemaField.getFieldType();
		String indexed = document.get(fieldName);
		if (StringUtils.isBlank(indexed)) {
			return null;
		}
		String readable = fieldType.indexedToReadable(indexed);
		return readable;
	}

	public static CLong getLong(String indexed, IndexSchema indexSchema) {
		String readable = indexedToReadable(ID_FIELD, indexed, indexSchema);
		if (StringUtils.isBlank(readable)) {
			return null;
		}
		return new CLong(readable);
	}

	public static String getGUID(Document document, IndexSchema indexSchema) {
		return indexedToReadable(GUID_FIELD, document, indexSchema);
	}

	public static CLong getID(Document document, IndexSchema indexSchema) {
		String readable = indexedToReadable(ID_FIELD, document, indexSchema);
		if (StringUtils.isBlank(readable)) {
			return null;
		}
		return new CLong(readable);
	}

	public static TransactionID getTransactionID(Document document, IndexSchema indexSchema) {
		String readable = indexedToReadable(TRANSACTION_ID_FIELD, document, indexSchema);
		if (StringUtils.isBlank(readable)) {
			return null;
		}
		return new TransactionID(readable);
	}

	public static TransactionID getCellTransactionID(Document document, IndexSchema indexSchema) {
		String readable = indexedToReadable(CELL_TRANSACTION_ID_FIELD, document, indexSchema);
		if (StringUtils.isBlank(readable)) {
			return null;
		}
		return new TransactionID(readable);
	}

	public static void loadStatics() {
	}

	static {
		System.setProperty("java.net.preferIPv4Stack", "true");
		System.setProperty("javax.xml.xpath.XPathFactory", "com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl");
		System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
		System.setProperty("javax.xml.parsers.SAXParserFactory", "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
	}
}
